#region File Description
//-----------------------------------------------------------------------------
// Author: JCBDigger
// URL: http://Games.DiscoverThat.co.uk
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
using System;
using System.Threading;
using AssetData;
using AssetContent;
#endregion

namespace Editor
{
    /// <summary>
    /// Helper to manage the configuable particle effect.
    /// </summary>
    public class ParticleHolder
    {
        private static Vector3 startCentrePosition = Vector3.Zero;
        private static Vector3 startLinePosition = new Vector3(10f, 0, 0);
        private static Vector3 lineAngle = new Vector3(-1f, 0.5f, 1f);
        private const float distanceAboveTheFloor = 3f;
        private const float circleRadius = 6f;

        /// <summary>
        /// The particle effect.
        /// </summary>
        private ParticleSystem particle;
        /// <summary>
        /// Where the particles appear from.
        /// </summary>
        private ParticleEmitter emitter = new ParticleEmitter();
        /// <summary>
        /// Get the position that the emitter is at.
        /// This is the current position if called after the emitter update
        /// or the position last frame if called before the emitter update.
        /// </summary>
        public Vector3 EmitterLocation
        {
            get { return emitter.PreviousPosition; }
        }
        /// <summary>
        /// The current image used by the particle effect.
        /// </summary>
        private Texture2D texture;
        /// <summary>
        /// The number of particles emitted persecond.
        /// </summary>
        private float particlesPerSecond = 30f;
        public float ParticlesPerSecond
        {
            get { return particlesPerSecond; }
            set 
            { 
                particlesPerSecond = value;
                emitter.ChangeParticlesPerSecond(particlesPerSecond);
            }
        }

        public enum EmitterMotion
        {
            Static,
            Line,
            Circle
        }
        /// <summary>
        /// The path of the emitter.
        /// </summary>
        private EmitterMotion motion = EmitterMotion.Static;
        public EmitterMotion Motion
        {
            get { return motion; }
            set { motion = value; }
        }

        public enum EmitterFlow
        {
            Continuous,
            Periodic
        }
        /// <summary>
        /// Do the particles come out evenly or in bursts.
        /// </summary>
        private EmitterFlow emitFlow = EmitterFlow.Continuous;
        public EmitterFlow EmitFlow
        {
            get { return emitFlow; }
            set { emitFlow = value; }
        }

        /// <summary>
        /// Speed and straight line direction of the emitter.
        /// </summary>
        private Vector3 emitterVelocity = Vector3.Zero;
        /// <summary>
        /// Rotational speed.
        /// </summary>
        private float speedPerSecond = 0;
        public float Speed
        {
            get { return speedPerSecond; }
            set
            {
                speedPerSecond = value;
                emitterVelocity = Vector3.Multiply(Vector3.Normalize(lineAngle), value);
            }
        }

        private TimeSpan emitEndTime = TimeSpan.Zero;
        private TimeSpan emitNextTime = TimeSpan.Zero;
        private float emitFrequency = 0;
        public float EmitFrequency
        {
            get { return emitFrequency; }
            set
            {
                emitFrequency = value;
                emitNextTime = TimeSpan.Zero;
            }
        }
        private float emitDuration = 0;
        public float EmitDuration
        {
            get { return emitDuration; }
            set
            {
                emitDuration = value;
                emitEndTime = TimeSpan.Zero;
            }
        }

        /// <summary>
        /// Set to true to reset the emiter to the origin of the line.
        /// </summary>
        private bool lineRestart = true;
        private TimeSpan lineStartTime = TimeSpan.Zero;

        private Random random = new Random();

        public ParticleHolder()
        {

        }

        /// <summary>
        /// Load the effect from the content manager and create the particle system.
        /// Uses a built in smoke effect.
        /// </summary>
        public void LoadInitialContent(ModelViewerControl modelViewerControl)
        {
            Loader contentLoad = new Loader(modelViewerControl.Services);
            Effect particleEffect = contentLoad.GetEffect("ParticleEffect");
            particle = new ParticleSystem(modelViewerControl.GraphicsDevice, particleEffect);
            ParticleSettings settings = contentLoad.GetParticleSettings("SmokePlume");
            SetParticleSettings(settings, true);
            Texture2D image = contentLoad.GetImage(settings.GetAssetNameWithContentPath());
            SetParticleTexture(image, settings.GetFileName());
            Reactivate();
        }

        /// <summary>
        /// Change the effect settings.
        /// Changing the buffer size will reset the particle.
        /// </summary>
        public void SetParticleSettings(ParticleSettings settings, bool changeBufferSize)
        {
            if (changeBufferSize)
            {
                emitter.Deactivate();
            }
            particle.ChangeParticleSettings(settings, changeBufferSize);
            if (changeBufferSize)
            {
                Reactivate();
            }
        }

        /// <summary>
        /// Change the image used by the effect.
        /// </summary>
        public void SetParticleTexture(Texture2D image, string fileName)
        {
            texture = image;
            particle.ChangeParticleTexture(image);
            particle.ChangeTextureName(fileName);
            Reactivate();
        }

        public Texture2D GetCurrentTexture()
        {
            return texture;
        }

        public string GetTextureName()
        {
            return particle.Settings.TextureName;
        }

        public ParticleSettings GetParticleSettings()
        {
            return particle.Settings;
        }

        public void Update(GameTimeMirror gameTime)
        {
            UpdateTimes(gameTime);
            if (IsEmitterEmitting(gameTime))
            {
                switch (motion)
                {
                    case EmitterMotion.Circle:
                        emitter.Update(gameTime, CircularPosition(gameTime), random);
                        break;
                    case EmitterMotion.Line:
                        emitter.Update(gameTime, LinePosition(gameTime), random);
                        break;
                    default:
                        emitter.Update(gameTime, startCentrePosition, random);
                        break;
                }
            }

            if (particle != null)
            {
                particle.Update(gameTime);
            }
        }

        private void UpdateTimes(GameTimeMirror gameTime)
        {
            if (gameTime.TotalGameTime > emitNextTime && emitDuration > 0)
            {
                emitEndTime = gameTime.TotalGameTime + TimeSpan.FromSeconds(emitDuration);
                emitNextTime = gameTime.TotalGameTime + TimeSpan.FromSeconds(emitFrequency);
                lineRestart = true;
            }
            if (emitFrequency > 0 && emitNextTime <= TimeSpan.Zero)
            {
                emitNextTime = gameTime.TotalGameTime + TimeSpan.FromSeconds(emitFrequency);
            }
            if (emitDuration > 0 && emitEndTime <= TimeSpan.Zero)
            {
                emitEndTime = gameTime.TotalGameTime + TimeSpan.FromSeconds(emitDuration);
            }
            if (lineStartTime <= TimeSpan.Zero)
            {
                lineStartTime = gameTime.TotalGameTime;
            }
        }

        public void Draw(GameTimeMirror gameTime, ref Matrix view, ref Matrix projection)
        {
            if (particle != null)
            {
                particle.Draw(gameTime, ref view, ref projection);
            }
        }

        /////////////////////////////////////////////////////////////////////
        //
        #region Helpers

        public void Reactivate()
        {
            emitter.Deactivate();
            Thread.Sleep(2);
            Activate();
        }

        private void Activate()
        {
            if (!emitter.Active && particle != null)
            {
                emitter.Activate(particle, particlesPerSecond, startCentrePosition);
            }
        }

        private bool IsEmitterEmitting(GameTimeMirror gameTime)
        {
            if (emitFlow == EmitterFlow.Continuous)
            {
                return true;
            }
            if (gameTime.TotalGameTime < emitEndTime)
            {
                return true;
            }
            return false;
        }

        private Vector3 CircularPosition(GameTimeMirror gameTime)
        {
            double angle = (double)gameTime.TotalGameTime.TotalSeconds * speedPerSecond;
            return new Vector3(
                ((float)Math.Sin(angle) * circleRadius) + startCentrePosition.X,
                distanceAboveTheFloor,
                ((float)Math.Cos(angle) * circleRadius) + startCentrePosition.Z);
        }

        private Vector3 LinePosition(GameTimeMirror gameTime)
        {
            if (lineRestart)
            {
                lineRestart = false;
                lineStartTime = gameTime.TotalGameTime;
                emitEndTime = gameTime.TotalGameTime + TimeSpan.FromSeconds(emitDuration);
                emitNextTime = gameTime.TotalGameTime + TimeSpan.FromSeconds(emitFrequency);
                return startCentrePosition;
            }
            float flightTime = (float)(gameTime.TotalGameTime - lineStartTime).TotalSeconds;
            return Vector3.Add(startLinePosition, Vector3.Multiply(emitterVelocity, flightTime));
        }

        #endregion
        //
        /////////////////////////////////////////////////////////////////////

    }
}
